package com.livestocksolutions;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import android.annotation.SuppressLint;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;

/* -- Version History --
 *  1.0 : 16 July 2012 - Initial version.
 *  
 *  1.1 : 16 July 2012 - Created protected methods boolean save(File) and boolean load(File) to handle saving and loading files
 *                       Changed boolean import(String) to boolean importData(String) because import is a reserved name. 
 *                       Changed arrays to ArrayList<T>
 * 
 *  1.2 : 05 September 2012 - Changed ArrayList<T> to become HashMap<Long,T>. Added accessor methods to return the particular
 *                       Object when given the id of that Object, or null if an Object of the requested type does not exist with
 *                       that id. Added methods to add extra items to the list, as long as their id's do not already exist in the
 *                       system.
 *  
 *  1.3 : 12 September 2012 - Added Methods to return a list of each Object type that this class holds.
 *  
 *  1.4 : 17 October 2012 - Added ability to load archived Objects.
 *  
 *  1.5 : 29 November 2012 - Added Date customisation ability. By default the value will be null, this has to be initialised by the
 *  					System it is being run on as there are differences between Android and Java for this. 
 */

/**
 * Class to provide access to all other stored objects that are used within this system
 * @author Adam Parr
 * 
 * @version 1.5
 * 
 * @.modified 07 March 2013
 */
@SuppressLint("SimpleDateFormat")
public class LivestockSolutions {
	/**
	 * Load the data to be ready for use.
	 * @return <tt>true</tt> if this is the only instance of this program currently running.
	 * <tt>false</tt> if the program was not cleanly shutdown, or if another instance is running.
	 */
	public static boolean initilise(){
		System.out.println("Checking to see if directory is locked...");
		File lock = new File(getStorageLocation(),".lock");
		if(lock.exists()){
			locked=true;
			System.out.println("  Directory is locked.");
			return false;
		}
		try {
			lock.createNewFile();
			lock.deleteOnExit();
		} catch (IOException e) {/*IO Exception while trying to create .lock file*/}
		System.out.println("Safe to Proceed.");
		
		System.out.println("Loading Required Files...");
		System.out.println("  Loading Settings.");
		long start = new Date().getTime();
		if(!loadSettings()){
			System.out.println("    Creating Settings File.");
			if(!loadSettings()){
				System.out.println("!     Failed to Create Settings File.");
			}
		}
		System.out.println("  Loading Data.");
		if(!load()){
			System.out.println("    Creating Data File.");
			if(!load()){
				System.out.println("!     Failed to Create Data File.");
			}
			System.out.println("    Creating Archive File.");
			if(!archive()){
				System.out.println("!     Failed to Archive File.");
			}
		}
		System.out.println("  Details:");
		System.out.println("    |-  "+ cattle.size() +" Cattle Loaded");
		System.out.println("    |-  "+ cattleBreeds.size() +" Cattle Breeds Loaded");
		System.out.println("    |-  "+ groups.size() +" Mobs/Groups Loaded");
		System.out.println("    |-  "+ locations.size() +" Locations/Paddocks Loaded");
		System.out.println("    |-  "+ joinings.size() +" Joinings Loaded");
		System.out.println("    |-  "+ observations.size() +" Observations Loaded");
		System.out.println("    |-  "+ usableItems.size() +" Feeds/Chemical/Medicines Loaded");
		System.out.println("    |-  "+ owners.size() +" People details Loaded");
		System.out.println("   _|_  ");
		System.out.println("Took: "+ (new Date().getTime()-start) +"ms to load.");
		
		System.out.println("Checking for Automated backup settings...");
		Calendar c = Calendar.getInstance();
		c.add(Calendar.DATE, -backupPeriodInDays);
		if(backupPeriodInDays>0 && c.getTime().after(new Date(lastBackup))){
			if(backup()){
				System.out.println("  Created Backup.");
			}else{
				System.out.println("  Automated Backup failed.");
				message="Automated Backup failed.";
			}
		}else{
			System.out.println("No need to backup.");
		}
		return loaded = true;
	}
	/**
	 * A flag to indicate to active threads whether or not to close.
	 */
	public static boolean close = false;
	protected static boolean locked= false;
	protected static long lastSave = 0;
	protected static boolean threadActive = false;
	protected static boolean loaded = false;
	/**
	 * A Log of messages that can be shown after the completion of particular events.<br>
	 * Currently only available for {@link #importData(String)}
	 */
	protected static String log = "";
	/**
	 * The last message that was created by an activity. Use this to display messages to the user.
	 */
	protected static String message = "";
	/**
	 * String of the path to which the backup files should be saved to.
	 */
	protected static String backupPath;
	/**
	 * The number of days that must pass before beginning an automatic backup.<br>
	 * A value of 0 or less indicates no automatic backup will occur.
	 */
	protected static int backupPeriodInDays;
	/**
	 * The date of the last backup that occurred .
	 */
	protected static long lastBackup;
	/**
	 * The last Sync path to try before asking the user for the path again.
	 */
	protected static String lastSyncPath;
	/**
	 * File location where internal files will be saved to.
	 */
	protected static File storagePath;
	/**
	 * A String representation of the date format pattern.
	 */
	protected static String dateFormat;
	
	/**
	 * The Colour to set the Header portion of the list.
	 */
	public final static int HEADER = 0;
	/**
	 * The Colour to set the Header column of the list that the list is ordered by.
	 */
	public final static int HEADER_ORDERED = 1;
	/**
	 * The Colour to define alternative rows in the List.
	 */
	public final static int LIST_A1 = 2;
	/**
	 * The Colour to define alternative rows in the List.
	 */
	public final static int LIST_B1 = 3;
	/**
	 * The Colour to define alternative rows in the List, that are adjacent to {@link #LIST_A1}.
	 */
	public final static int LIST_A2 = 4;
	/**
	 * The Colour to define alternative rows in the List, that are adjacent to {@link #LIST_B1}.
	 */
	public final static int LIST_B2 = 5;
	/**
	 * The Colour to set the List item when the Mouse hover event is triggered.
	 */
	public final static int LIST_HOVER = 6;
	/**
	 * The Colour to set the List item when the Mouse down event is triggered.
	 */
	public final static int LIST_CLICK = 7;
	
	/**
	 * Colour settings, Storing the colour as a Hexadecimal colour put({@link #HEADER},"#000000").
	 * @see #HEADER
	 * @see #HEADER_ORDERED
	 * @see #LIST_A1
	 * @see #LIST_B1
	 * @see #LIST_A2
	 * @see #LIST_B2
	 * @see #LIST_HOVER
	 * @see #LIST_CLICK
	 */
	protected static HashMap<Integer,String> colourSettings = new HashMap<Integer,String>();
	
	/**
	 * Settings for the application that may relate to specific cases in the GUI.<br>
	 * an example may be Window width and height as well as position and last tab and Object shown.
	 */
	protected static HashMap<String,Object> programSettings = new HashMap<String,Object>();
	
	/**
	 * Application directory name.
	 */
	protected static final String APPDIR        = "Livestock Solutions";
	/**
	 * Archive file name.
	 */
	protected static final String ARCHIVE       = "archive.dat";
	/**
	 * Data file name.
	 */
	protected static final String DATAFILE      = "data.dat";
	/**
	 * Configuration file name.
	 */
	protected static final String SETTINGS      = "settings.cfg";
	/**
	 * Backup file name.<br>
	 * <tt>%1$s</tt> is replaced by the current date.
	 */
	protected static final String BACKUP        = "LivestockSolutions_backup_%1$s.zip";
	/**
	 * Used to identify a particular version of the {@link #DATAFILE} and determine if the file is a valid file to use.
	 */
	protected static final String DATAFILEIDENT = "Livestock Solutions Data File";
	
	/**
	 * The version of the data file this application expects to create and read.
	 */
	protected static final double DATAFILEVERSION = 1.10;
	
	/**
	 * The time the Data file was last modified.
	 */
	private static long lastModified = 0;
	/**
	 * Progress meter in which can be used to graphically show progress of any of the backend actions
	 * that may occur.
	 */
	protected static int progress = 0;
	/**
	 * The ratio to increase the progress by.<br>
	 * 1 is normal, 0.5 is 50% 0.0 is 0%
	 */
	protected static double scale = 1;
	/**
	 * The amount at which the progress will be offset by
	 */
	protected static int offset = 0;
	/**
	 * List of cattle breeds within the system.
	 */
	protected static HashMap<Long,CattleBreed> cattleBreeds  = new HashMap<Long,CattleBreed>();
	/**
	 * List of all cattle in the system.
	 */
	protected static HashMap<Long,Bovine> cattle = new HashMap<Long,Bovine>();
	/**
	 * List of Mobs and Groups in the system.
	 */
	protected static HashMap<Long,Group> groups = new HashMap<Long,Group>();
	/**
	 * List of all Joins within the system.
	 */
	protected static HashMap<Long,Join> joinings = new HashMap<Long,Join>();
	/**
	 * List of all Locations and Paddocks within the system.
	 */
	protected static HashMap<Long,Location> locations = new HashMap<Long,Location>();
	/**
	 * All Owners, Business, Farms that have been added to the system.
	 */
	protected static HashMap<Long,Owner> owners = new HashMap<Long,Owner>();
	/**
	 * All Items (UsableItems) that have been added to the system.
	 */
	protected static HashMap<Long,UsableItem<? extends UsedItem>> usableItems = new HashMap<Long,UsableItem<? extends UsedItem>>();
	/**
	 * All Observations that have been added to the system.
	 */
	protected static HashMap<Long,Observation> observations = new HashMap<Long,Observation>();
	
	/**
	 * List of all cattle that have been archived from the system.
	 */
	protected static HashMap<Long,Bovine> cattleArchive = new HashMap<Long,Bovine>();
	/**
	 * List of all Joins that have been archived from the system.
	 */
	protected static HashMap<Long,Join> joiningsArchive = new HashMap<Long,Join>();
	/**
	 * All Observations that have been archived from the system.
	 */
	protected static HashMap<Long,Observation> observationsArchive = new HashMap<Long,Observation>();
	/**
	 * All Items (UsableItems)  that have been archived from the system.
	 */
	protected static HashMap<Long,UsableItem<? extends UsedItem>> usableItemsArchive = new HashMap<Long,UsableItem<? extends UsedItem>>();
	
	/**
	 * Get the current progress position of the current activity.<br>
	 * A value of -1 indicates the Activity has yet to start or is requesting User Input or failed.<br>
	 * A value between 0 and 100 inclusive indicates that the current activity is currently running.<br>
	 * A value of 101 indicates this activity has been completed.<br>
	 * <br>
	 * After reaching 101, upon calling this method, the progress value will reset to -1, with subsequent
	 * calls returning -1 until a new activity is started. If the activity successfully completes, 101 is always
	 * guaranteed to be returned.
	 * @return An Integer value between -1 and 101 to signify the current completeness of the activity.
	 */
	public static int getProgress(){
		int prog = progress;
		progress=progress==101?-1:progress;
		return prog;
	}
	
	public static void resetProgress(){
		progress=0;
	}
	
	/**
	 * Force the application to ignore any other instances of this program.<br>
	 * This is a manual override in case any even occurs where by the ".lock" file is not
	 * deleted before closing.
	 */
	public static void ignoreLock(){
		File lock = new File(getStorageLocation(),".lock");
		locked = false;
		lock.delete();
		initilise();
	}
	
	/**
	 * Checks to see if the Directory is in use.
	 * @return <tt>true</tt> if another application has locked the file directory, <tt>false</tt> otherwise.
	 */
	public static boolean isLocked(){
		return locked;
	}
	
	/**
	 * Checks to see if the data has been loaded.
	 * @return <tt>true</tt> if the data has been loaded, <tt>false</tt> otherwise.
	 */
	public static boolean isReady(){
		return loaded;
	}
	
	/**
	 * Get the backup period
	 * @return The number of days that should pass since the last backup before performing an automated backup.<br>
	 * A value of 0 or less indicates no automatic backup
	 */
	public static int getBackupPeriod(){
		return backupPeriodInDays;
	}
	
	/**
	 * Get the date of the last backup
	 * @return The Date of the last backup that occurred
	 */
	public static Date getLastBackup(){
		return lastBackup!=0?new Date(lastBackup):null;
	}
	
	/**
	 * Get the last path that was used to synchronise to the Android device.
	 * @return A string representing the absolute path to where the "android" was mounted to.
	 */
	public static String getLastSyncPath(){
		return lastSyncPath;
	}
	
	/**
	 * Get the backup path
	 * @return A string representing the absolute path to where the backups are saved to.
	 */
	public static String getBackupPath(){
		return backupPath;
	}
	
	/**
	 * Get a Group object
	 * @param group The ID of the Group Object to get
	 * @return The Group object if an Object of that ID exists, or <code>null</code> if none exist.
	 */
	public static Group getGroupByID(long group){
		return groups.get(group);
	}
	
	/**
	 * Get a Join object
	 * @param join The ID of the Join Object to get
	 * @return The Join object if an Object of that ID exists, or <code>null</code> if none exist.
	 */
	public static Join getJoiningByID(long join){
		Join j = joinings.get(join);
		return j!=null?j:joiningsArchive.get(join);
	}
	
	/**
	 * Get a Bovine object
	 * @param bovine The ID of the Bovine Object to get
	 * @return The Bovine object if an Object of that ID exists, or <code>null</code> if none exist.
	 */
	public static Bovine getBovineByID(long bovine){
		Bovine b = cattle.get(bovine);
		return b!=null?b:cattleArchive.get(bovine);
	}
	
	/**
	 * Get a Location object
	 * @param location The ID of the Location Object to get
	 * @return The Location object if an Object of that ID exists, or <code>null</code> if none exist.
	 */
	public static Location getLocationByID(long location){
		return locations.get(location);
	}
	
	/**
	 * Get a Owner object
	 * @param owner The ID of the Owner Object to get
	 * @return The Owner object if an Object of that ID exists, or <code>null</code> if none exist.
	 */
	public static Owner getOwnerByID(long owner){
		return owners.get(owner);
	}
	
	/**
	 * Get a UsableItem object
	 * @param usableItem The ID of the UsableItem Object to get
	 * @return The UsableItem object if an Object of that ID exists, or <code>null</code> if none exist.
	 */
	public static UsableItem<? extends UsedItem> getUsableItemByID(long usableItem){
		return usableItems.get(usableItem);
	}
	
	/**
	 * Get an Observation object
	 * @param observation The ID of the Observation Object to get
	 * @return The Observation object if an Object of that ID exists, or <code>null</code> if none exist.
	 */
	public static Observation getObservationByID(long observation){
		Observation o = observations.get(observation);
		return o!=null?o:observationsArchive.get(observation);
	}
	
	/**
	 * Get a breed information that is identified by the name given by breedName.
	 * @param breedName The name of the breed to return
	 * @return CattleBreed representing a specific breed of cattle,
	 * or null if no matches were found.
	 */
	public static CattleBreed getCattleBreed(String breedName){
		breedName=breedName==null?"":breedName;
		if(cattleBreeds!=null){
			for(CattleBreed cb : cattleBreeds.values()){
				if(lowerCase(cb.getBreedName()).equals(lowerCase(breedName))){
					return cb;
				}
			}
		}
		return null;
	}
	
	/**
	 * Get a CattleBreed object
	 * @param breed The ID of the Breed Object to get
	 * @return CattleBreed representing a specific breed of cattle,
	 * or null if no matches were found.
	 */
	public static CattleBreed getCattleBreedByID(long breed){
		return cattleBreeds.get(breed);
	}
	
	/**
	 * Set the backup path to the one provided
	 * @param backupPath String representation of the path where backups are saved
	 */
	public static void setBackupPath(String backupPath){
		File f = new File(backupPath);
		LivestockSolutions.backupPath=f.isFile()?f.getParentFile().getAbsolutePath():backupPath;
		saveSettings();
	}
	
	/**
	 * Set the number of days to wait since the last backup before performing an automatic backup
	 * @param backupPeriodInDays backup after this many days have passed. A value of 0 or less
	 * represents no automatic backup.
	 */
	public static void setBackupPeriod(int backupPeriodInDays){
		LivestockSolutions.backupPeriodInDays=backupPeriodInDays;
		saveSettings();
	}
	
	/**
	 * Set the sync location to the path specified, this can be used to sync to an MTP device.
	 * @param path The path in which to sync to and from.
	 */
	public static void setSyncLocation(String path){
		lastSyncPath = path;
	}
	
	/**
	 * Add a new breed and information for this breed to this system
	 * @param name The name of the breed to add
	 * @param averageCalfBirthWeight Approximate birth weight of the breed to add
	 * @param averageFullWeight Approximate full grown weight of a Female for the breed to add
	 * @param averageMaleFullWeight Approximate full grown of a Male weight for the breed to add
	 * @param averageDailyGain Average weight gain between birth and pubity for a Female
	 * @param averageMaleDailyGain Average weight gain between birth and pubity for a Male
	 * @param gestationPeriodInDays Average time for gestation length in days
	 * @param averagePubityAgeInDays Average number of days until pubity begins for a Female
	 * @param averageMalePubityAgeInDays  Average number of days until pubity begins for a Male
	 * @return <code>true</code> if the new Breed was successfully added, <code>false</code> otherwise.
	 */
	public static boolean addCattleBreed(String name, float averageCalfBirthWeight, float averageFullWeight, float averageMaleFullWeight, float averageDailyGain, float averageMaleDailyGain, int gestationPeriodInDays, int averagePubityAgeInDays, int averageMalePubityAgeInDays){
		if(getCattleBreed(name)!=null){
			/* If this breed name already exists, should we update this breed or present the
			 * caller with the option to determine what should be done, such as alert the user
			 * this breed already exists, and allow some choices.
			 */
			//return false;
		}
		
		CattleBreed cb = new CattleBreed(name,
			averageCalfBirthWeight==0f?29.71F:averageCalfBirthWeight,
			averageFullWeight==0f?680:averageFullWeight,
			averageMaleFullWeight==0?680:averageMaleFullWeight,
			averageDailyGain==0f?1.24F:averageDailyGain,
			averageMaleDailyGain==0f?1.24F:averageMaleDailyGain,
			gestationPeriodInDays==0?282:gestationPeriodInDays,
			averagePubityAgeInDays==0?365:averagePubityAgeInDays,
			averageMalePubityAgeInDays==0?365:averageMalePubityAgeInDays);
		cattleBreeds.put(cb.getID(),cb);
		saveWait();
		return true;
	}
	
	/**
	 * Adds a Group to the list of Groups in the System.
	 * @param group The Group to add
	 * @return <code>true</code> if a Group with the same id does not exist and group was added
	 *          successfully, <code>false</code> otherwise
	 */
	public static boolean addGroup(Group group){
		for(Group g : groups.values()){
			if(lowerCase(g.getName()).equals(lowerCase(group.getName()))&& g.getID()!=group.getID()){
				return false;
			}
		}
		groups.put(group.getID(),group);
		saveWait();
		return true;
	}
	
	/**
	 * Adds a Bovine to the list of Cattle in the System.
	 * @param bovine The Bovine to add
	 * @return <code>true</code> if a Group with the same id does not exist and group was added
	 *          successfully, <code>false</code> otherwise
	 */
	public static boolean addBovine(Bovine bovine){
		if(getBovineByID(bovine.getID())!=null){
			/* If this bovine already exists, should we update this bovine or present the
			 * caller with the option to determine what should be done, such as alert the user
			 * this bovine with the same id already exists, and allow some choices.
			 */
		//	if((bovine.getDob()==null && getBovineByID(bovine.getID()).getDob()==null) || (bovine.getDob()!=null && getBovineByID(bovine.getID()).getDob()!=null && !getBovineByID(bovine.getID()).getDob().equals(bovine.getDob()))){
		//		return false;
		//	}
			
			/* We have a Bovine with the same ID, and same DOB, merge these two. 
			 * If this is a Gender change, ignore it as everything is copied across before,
			 * hence Object references are the same.
			 */
			if(bovine!=getBovineByID(bovine.getID()) && bovine.getGender().equals(getBovineByID(bovine.getID()).getGender())){
				bovine.update(getBovineByID(bovine.getID()));
			}
		}
		cattle.put(bovine.getID(),bovine);
		saveWait();
		return true;
	}
	
	/**
	 * Adds a Join to the list of Joinings in the System.
	 * @param join The Join to add
	 * @return <code>true</code> if a Join with the same id does not exist and join was added
	 *          successfully, <code>false</code> otherwise
	 */
	public static boolean addJoining(Join join){
		if(join==null){
			return false;
		}
		joinings.put(join.getID(),join);
		saveWait();
		return true;
	}
	
	/**
	 * Adds a Location to the list of Locations in the System.
	 * @param location The Location to add
	 * @return <code>true</code> if a Location with the same id does not exist and location was added
	 *          successfully, <code>false</code> otherwise
	 */
	public static boolean addLocation(Location location){
		for(Location l : locations.values()){
			if(lowerCase(l.getName()).equals(lowerCase(location.getName()))&& l.getID()!=location.getID()){
				return false;
			}
		}
		locations.put(location.getID(),location);
		saveWait();
		return true;
	}
	
	/**
	 * Adds a Owner to the list of Owner in the System.
	 * @param owner The Owner to add
	 * @return <code>true</code> if a Owner with the same id does not exist and owner was added
	 *          successfully, <code>false</code> otherwise
	 */
	public static boolean addOwner(Owner owner){
		for(Owner o : owners.values()){
			if(lowerCase(o.getName()).equals(lowerCase(owner.getName()))&& o.getID()!=owner.getID()){
				// Should false be returned or should these be merged
				return false;
			}
		}
		owners.put(owner.getID(),owner);
		saveWait();
		return true;
	}
	
	/**
	 * Adds a UsableItem to the list of UsableItems in the System.
	 * @param usableItem The UsableItem to add
	 * @return <code>true</code> if an item with the same id does not exist and usableItem was added
	 *          successfully, <code>false</code> otherwise
	 */
	public static boolean addUsableItem(UsableItem<? extends UsedItem> usableItem){
		if(usableItem==null){
			return false;
		}
		usableItems.put(usableItem.getID(),usableItem);
		saveWait();
		return true;
	}
	
	/**
	 * Adds an Observation to the list of Observations in the System.
	 * @param observation The Observation to add
	 * @return <code>true</code> if an item with the same id does not exist and observation was added
	 *          successfully, <code>false</code> otherwise
	 */
	public static boolean addObservation(Observation observation){
		if(observation==null){
			return false;
		}
		observations.put(observation.getID(),observation);
		saveWait();
		return true;
	}
	
	/**
	 * Removes a cattle breed from this system. It is not recommended to remove a breed if it has
	 * been used at any point.
	 * @param name The name of the breed to remove
	 * @return <code>true</code> if the new Breed was successfully removed, <code>false</code> otherwise.
	 */
	public static boolean removeCattleBreed(String name){
		CattleBreed cb = getCattleBreed(name);
		if(cb!=null){
			for(Bovine b: cattle.values()){
				if(b.getBreed().equals(cb)){
					// This breed has already been used, do not allow deletion
					return false;
				}
			}
			cattleBreeds.remove(cb.getID());
			saveWait();
			return true;
		}
		return false;
	}
	
	/**
	 * Checks to see if the data file has been updated since loading it before.
	 * @return <tt>true</tt> if the file has a later modification date than the previous stored value.
	 */
	public static boolean checkUpdatedData(){
		if(new File(getStorageLocation(),DATAFILE).lastModified()>lastModified){
			load();
			return true;
		}
		return false;
	}
	
	/**
	 * Backup the system data to the set location
	 * @return <code>true</code> if backup was successful, <code>false</code> if there was an error.
	 */
	public static boolean backup(){
		progress=offset+(int) (0*scale);
		// Save the files as they currently exist...
		File backupLoc = new File(backupPath);
		File backupFile = new File(backupLoc,BACKUP.replace("%1$s", new SimpleDateFormat(dateFormat).format(new Date()).replaceAll("/","_")));
		System.out.println("Backing up to: "+backupFile.getAbsolutePath());
		progress=offset+(int) (10*scale);
		scale=0.3;
		offset=progress;
		save();
		offset=0;
		scale=1;
		progress=offset+(int) (40*scale);
		String[] filenames = new String[]{ARCHIVE, DATAFILE, SETTINGS};
		byte[] buf = new byte[1024];
		try {
			backupLoc.mkdirs();
			progress=offset+(int) (50*scale);
		    ZipOutputStream zot = new ZipOutputStream(new FileOutputStream(backupFile));
		    for (int i=0; i<filenames.length; i++) {
		    	File f = new File(getStorageLocation(),filenames[i]);
		    	if(f.exists()) {
			    	FileInputStream fin = new FileInputStream(f);
			    	zot.putNextEntry(new ZipEntry(filenames[i]));
			        int len;
			        while ((len=fin.read(buf))>0){
			        	zot.write(buf,0,len);
			        }
	
			        zot.closeEntry();
			        fin.close();
		    	}
		        progress=offset+(int) (((((i*100)/filenames.length)/2)+50)*scale);
		    }
		    zot.close();
		    progress=offset+(int) (90*scale);
		} catch (IOException e){
			System.out.println("Backup Failed");
			message=e.getMessage();
			progress=-1;
			return false;
		}
		lastBackup=new Date().getTime();
		offset=90;
		scale=0.1;
		saveSettings();
		offset=0;
		scale=1;
		progress=offset+(int) (100*scale);
		System.out.println("Backup Successful");
		message=backupFile.getAbsolutePath();
		progress=101;
		return true;
	}
	
	/**
	 * Restore the system data from the provided file location
	 * @param filePath The path at which the file at this location will attempt to be loaded to restore
	 * data contained within
	 * @return <code>true</code> if backup was successful
	 */
	public static boolean restore(String filePath){
		message="";
		progress=offset+(int) (0*scale);
		// First Open zip and check this is a correct file...
		System.out.println("Restore from Backup: "+ filePath);
		File backupToRestore = new File(filePath);
		if(backupToRestore.exists()){  
			progress=offset+(int) (10*scale);
			try {
				ZipFile zipFile = new ZipFile(backupToRestore);
				Enumeration<? extends ZipEntry> zip = zipFile.entries();
				int i=0;
				while(zip.hasMoreElements()){
					ZipEntry entry = zip.nextElement();
					File destinationFilePath = new File(getStorageLocation(),entry.getName());
					destinationFilePath.getParentFile().mkdirs();
					if(entry.isDirectory()){
						continue;
					}
					BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entry));
					int b;
					byte buffer[] = new byte[1024];
					
					BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destinationFilePath),1024);
					while ((b = bis.read(buffer, 0, 1024)) != -1){
						bos.write(buffer, 0, b);
					}
					
					bos.flush();
					bos.close();
					
					bis.close();
					progress=offset+(int) (((((i*100)/zipFile.size())/2)+10)*scale);
					i++;
				}
				zipFile.close();
				// Load the new data
				offset=50;
				scale=0.05;
				progress=offset+(int) (50*scale);
				if(!loadSettings()){
					System.out.println("!  Failed to restore settings from backup");
				}
				offset=0;
				scale=1;
				progress=offset+(int) (55*scale);
				offset=55;
				scale=0.44;
				load();
				offset=0;
				scale=1;
				System.out.println("Restore Successful");
				progress=offset+(int) (101*scale);
				return true;
			} catch (Exception e) {
				// Don't delete current files as this may not be the right file.
				e.printStackTrace();
				message=message==""?e.getMessage():"";
			}
		}
		System.out.println("Restore Failed");
		progress=-1;
		message=message==""?"Backup file does not exist.":message;
		return false;
	}
	
	/**
	 * Archive Objects that are no longer used or very old to reduce the size and amount of processing to
	 * generate the current list and interactions between current Objects.
	 * @return  <code>true</code> if archival was successful.
	 */
	public static boolean archive(){
		message="";
		progress=offset+(int) (0*scale);
		File file = new File(getStorageLocation(),ARCHIVE);
		Gson gson = new Gson();
		// Split Cattle HashMap into Bovine, Cow and Bull to keep these types
		HashMap<Long,Bovine> bovine = new HashMap<Long,Bovine>();
		HashMap<Long,Cow> cow = new HashMap<Long,Cow>();
		HashMap<Long,Bull> bull = new HashMap<Long,Bull>();
		HashMap<Long,Observation> observation = new HashMap<Long,Observation>();
		HashMap<Long,Join> joining = new HashMap<Long,Join>();

		progress=offset+(int) (10*scale);
		if(cattleArchive.size()==0)
			loadArchive();
		for(Bovine b : cattleArchive.values()){
			if(cattle.containsKey(b.getID())){
				cattle.get(b.getID()).update(b);
			}else{
				cattle.put(b.getID(),b);
			}
				
		}
		progress=offset+(int) (14*scale);
		for(Observation o : observationsArchive.values()){
			if(observations.containsKey(o.getID())){
				observations.get(o.getID()).update(o);
			}else{
				observations.put(o.getID(),o);
			}
				
		}
		progress=offset+(int) (18*scale);
		for(Join j : joiningsArchive.values()){
			progress=offset+(int) (18*scale);
			if(joinings.containsKey(j.getID())){
				joinings.get(j.getID()).update(j);
			}else{
				joinings.put(j.getID(),j);
			}
				
		}
		progress=offset+(int) (20*scale);
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.YEAR, -1);
		Iterator<Bovine> bi = cattle.values().iterator();
		while (bi.hasNext()) {
			Bovine b = bi.next();
			boolean isActive = b.isActive();
			if(!b.isActive()){
				for(Bovine o :b.getOffspring()){
					if(o.isActive()){
						isActive = true;
						break;
					}
				}
				if(!b.getDeacquisition().getDate().before(cal.getTime())){
					// Consider still active.
					isActive = true;
				}
			}
			if(!isActive){
				// Remove this Bovine from the list, and add to Archive File
				if(b instanceof Cow){
					cow.put(b.getID(), (Cow)b);
				}else if(b instanceof Bull){
					bull.put(b.getID(), (Bull)b);
				}else{
					bovine.put(b.getID(), b);
				}
				bi.remove();
			}
		}

		progress=offset+(int) (50*scale);
		// Remove all old Observations;
		Iterator<Observation> oi = observations.values().iterator();
		while (oi.hasNext()) {
			Observation ob = oi.next();
			if(ob.getOwner()==null){
				// The Owner is not loaded in the Object list
				observation.put(ob.getID(),ob);
				oi.remove();
			}
		}
		progress=offset+(int) (60*scale);
		// Remove any old Joinings
		Iterator<Join> ji = joinings.values().iterator();
		while (ji.hasNext()) {
			Join jo = ji.next();
			boolean inUse = false;
			if((jo.getBull()==null || !jo.getBull().isActive())){
				for(Cow c : jo.getCows()){
					if(c!=null && c.isActive()){
						inUse = true;
						break;
					}
				}
				if(!inUse){
					joining.put(jo.getID(),jo);
					ji.remove();
				}
			}
		}
		progress=offset+(int) (75*scale);
		
		// Save Archive File
		try {
			ObjectOutputStream oos = new ObjectOutputStream(new GZIPOutputStream(new FileOutputStream(file)));
			oos.writeObject(gson.toJson(bovine,new TypeToken<HashMap<Long,Bovine>>() {}.getType()));
			oos.writeObject(gson.toJson(bull,new TypeToken<HashMap<Long,Bull>>() {}.getType()));
			oos.writeObject(gson.toJson(cow,new TypeToken<HashMap<Long,Cow>>() {}.getType()));
			oos.writeObject(gson.toJson(observation,new TypeToken<HashMap<Long,Observation>>() {}.getType()));
			oos.writeObject(gson.toJson(joining,new TypeToken<HashMap<Long,Join>>() {}.getType()));
			oos.close();
		progress=offset+(int) (80*scale);
		offset=80;
		scale=0.1;
			save();
		offset=0;
		scale=1;
		offset=90;
		scale=0.1;
		loadArchive();
		offset=0;
		scale=1;
		} catch (IOException e) {
			e.printStackTrace();
			progress=-1;
			message="A problem occurred while trying to Archive.";
			return false;
		}
		progress=offset+(int) (101*scale);
		return true;
	}
	
	/**
	 * Load any archived Objects so that they can be referenced in the program.
	 * @return <tt>true</tt> if the Archive file was loaded without any errors, <tt>false</tt> Otherwise.
	 */
	public static boolean loadArchive(){
		message="";
		progress=offset+(int) (0*scale);
		File file = new File(getStorageLocation(),ARCHIVE);
		progress=offset+(int) (10*scale);
		// Open Archive File.
		if(file.exists()){
			try {
				progress=offset+(int) (10*scale);
				ObjectInputStream ois = new ObjectInputStream(new GZIPInputStream(new FileInputStream(file)));
				Gson gson = new Gson();
				cattleArchive 		= (HashMap<Long,Bovine>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Bovine>>(){}.getType());
				progress=offset+(int) (20*scale);
				cattleArchive.putAll((HashMap<Long,Bovine>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Bull>>(){}.getType()));
				progress=offset+(int) (35*scale);
				cattleArchive.putAll((HashMap<Long,Bovine>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Cow>>(){}.getType()));
				progress=offset+(int) (50*scale);
				joiningsArchive 	= (HashMap<Long,Join>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Join>>(){}.getType());
				progress=offset+(int) (70*scale);
				observationsArchive	= (HashMap<Long,Observation>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Observation>>(){}.getType());
				progress=offset+(int) (100*scale);
				
				ois.close();
				lastModified= file.lastModified();
				progress=offset+(int) (101*scale);
				return true;
			} catch (IOException e) {
				message="This File does not appear to be a valid Livestock Solutions data file";
				e.printStackTrace();
			} catch (JsonSyntaxException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}else{
			message="No file found at path";
		}
		return false;
	}
	
	/**
	 * Add additional Objects to the system, The file format is expected to be a CSV, supporting either the
	 * weights to add, or new animals to add to the system.<br>
	 * <p>
	 * An example of a weight CSV would be:
	 * <pre>
	 * Date: 31/12/2012
	 * ID,Weight
	 * AA01,150
	 * AA02,200
	 * AA03,160
	 * AA04,195
	 * AA05,210
	 * AA06,230
	 * </pre>
	 * </p>
	 * <hr>
	 * <p>
	 * <b>Importing cattle</b>:<br>
	 * <b>Required fields</b>:<br>
	 * <code>breed</code> : Breed name of this animal.<br>
	 * <br>
	 * <b>Other optional fields are</b>:<br>
	 * <code>id</code> : The Visual tag id of the new animal.<br>
	 * <code>nlis</code> : The NLIS tag id of the new animal.<br>
	 * <code>rf</code> : The Radio Frequency of the new animal.<br>
	 * <code>dob</code> : Date of Birth of the new animal.<br>
	 * <code>gender</code> : The Gender of this animal: either of "<code>Male</code>",
	 *                "<code>Female</code>" or "<code>Unknown</code>".<br>
	 * <code>dame</code> : The ID (either of visual id (id), NLIS or RF ids) of the mother of this animal.
	 * 						This animal must exist in the system before it is added to the Offspring.<br>
	 * <code>sire</code> : The ID (either of visual id (id), NLIS or RF ids) of the father of this animal.
	 * 						This animal must exist in the system before it is added to the Offspring.<br>
	 * <code>price</code> : If this animal was purchased, the price the animal was purchased for.<br>
	 * <code>previous owner</code> : If this animal was purchased, the name of the previous Owner. 
	 *                  If this name could not be found, a new entry will be created, with the details for
	 *                  this new owner being able to be filled out at a later date.<br>
	 * <code>date</code> : The date the animal was acquired, for a birth this will default to the DOB if 
	 *                  not provided<br>
	 * <code>mob</code> : Add this animal to this mob if it exists, or create a new mob and add it.<br>
	 * <code>group</code> : A single appearance of this can appear at any position in the list of column headers,
	 *                  but any left over field at the end of the list will be interpreted as a Group<br>
	 *                  
	 *  An example of a weight CSV would be:
	 * <pre>
	 * id  ,nlis           ,rf               ,dob      ,gender,breed,dame,sire,mob  ,price ,previous owner,date
	 * AA01,3VLBV29172G2837,10293827261526278,20/6/11  ,female,angus,    ,    ,Mob 1,      ,              ,          ,Group 1
	 * AA03,               ,                 ,10/6/2011,male  ,angus,    ,    ,Mob 2,150.00,Neil Goodman  ,20/05/2011
	 * AA02,               ,10293827261526172,22/6/12  ,      ,angus,AB01,AB03,Mob 1,      ,              ,          ,Calf of 2012, Group 2, Group 3...
	 * </pre>
	 * </p>
	 * @param filePath The path at which the file at this location will attempt to loaded the additional data
	 * @return <code>true</code> if data import was successful
	 */
	public static boolean importData(String filePath){
		progress=offset+(int) (0*scale);
		/* Check to see if any Objects will be overwritten, determine best method to overwrite that data
		 * if not then ask the user what data they want to keep, then act on that response.
		 * 
		 * While at this point, any modifications should occur on other ArrayLists, not the instances
		 * currently referenced in LivestockSolutions class, just in case of any problems, or maybe
		 * even a cancellation if that ability is provided.
		 * 
		 * At the end replace the instance Arrays LivestockSolutions class refers to, return true
		 */
		log="Importing Data from file: "+filePath;
		System.out.println(log);
		final int ID = 0;
		// Weight index locations
		final int WEIGHT = 1;
		
		// Cattle import index locations
		final int NLIS = 1;
		final int RF = 2;
		final int DOB = 3;
		final int GENDER = 4;
		final int BREED = 5;
		final int DAME = 6;
		final int SIRE = 7;
		final int PRICE = 8;
		final int PREVIOUSOWNER = 9;
		final int DATE = 10;
		final int MOB = 11;
		final int GROUP = 12;
		
		int added =0;
		int fail = 0;
		
		int[] columnPos=null;
		Date date = new Date();
		File file = new File(filePath);
		boolean weight = true;
		try {
			int length = count(filePath);
			BufferedReader br = new BufferedReader(new FileReader(file));
			String strLine, previousLine="";
			int i=0;
			int extras = 0;
			while ((strLine = br.readLine()) != null){
				if(!strLine.equals("")){
					if(!strLine.contains(",") && lowerCase(strLine).contains("date")){
						String line="";
						if(strLine.contains(":")){
							line=strLine.split(":")[1];
						}else{
							line=strLine.split("date")[1];
						}
						try{
							date = new SimpleDateFormat(dateFormat).parse(line.trim());
						}catch (ParseException e) {
							String m="Line says \"Date\", but does not appear to contain a valid date: "+line;
							log+="\n"+i+" : "+m;
							System.out.println(m);
							e.printStackTrace();
						}
					}else if(strLine.contains(",") || !strLine.trim().equals("")){
						// Search for a field that has numbers separated by commas 
						String[] columns = strLine.split(",");
						if(strLine.contains("breed") && (strLine.contains(",")) || !weight){
							// Could be a Cattle import csv
							if(columnPos==null){
								// Column row header must be the previous line;
								columnPos = new int[]{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
								String[] columnHeader = strLine.split(",");
								for(int x=0; x<columnHeader.length; x++){
									columnHeader[x]=lowerCase(columnHeader[x]);
									if(columnHeader[x].contains("id")){
										columnPos[ID]=x;
										System.out.println("Found ID column: "+x);
									}else if(columnHeader[x].equals("nlis")){
											columnPos[NLIS]=x;
											extras++;
											System.out.println("Found NLIS column: "+x);
									}else if(columnHeader[x].equals("rf")){
										columnPos[RF]=x;
										extras++;
										System.out.println("Found RFID column: "+x);
									}else if(columnHeader[x].equals("dob")){
										columnPos[DOB]=x;
										extras++;
										System.out.println("Found DOB column: "+x);
									}else if(columnHeader[x].equals("gender")){
										columnPos[GENDER]=x;
										extras++;
										System.out.println("Found Gender column: "+x);
									}else if(columnHeader[x].equals("breed")){
										columnPos[BREED]=x;
										extras++;
										System.out.println("Found Breed column: "+x);
									}else if(columnHeader[x].equals("dame")){
										columnPos[DAME]=x;
										extras++;
										System.out.println("Found Dame column: "+x);
									}else if(columnHeader[x].equals("sire")){
										columnPos[SIRE]=x;
										extras++;
										System.out.println("Found Sire column: "+x);
									}else if(columnHeader[x].equals("price")){
										columnPos[PRICE]=x;
										extras++;
										System.out.println("Found Price column: "+x);
									}else if(columnHeader[x].contains("owner")){
										columnPos[PREVIOUSOWNER]=x;
										extras++;
										System.out.println("Found Previous Owner column: "+x);
									}else if(columnHeader[x].equals("date")){
										columnPos[DATE]=x;
										extras++;
										System.out.println("Found Acquire Date column: "+x);
									}else if(columnHeader[x].equals("mob")){
										columnPos[MOB]=x;
										extras++;
										System.out.println("Found Mob column: "+x);
									}else if(columnHeader[x].contains("group") && columnPos[GROUP]==-1){
										columnPos[GROUP]=x;
										extras++;
										System.out.println("Found Group column: "+x);
									}
								}
								boolean valid = false;
								for(int a : columnPos){
									if(a!=-1){
										valid=true;
										break;
									}
								}
								columnPos=!valid?null:columnPos;
							}else{
								// Create a new Cattle with the given data
								boolean found = false;
								if((columnPos[ID]!=-1&&columnPos[ID]<columns.length&&!columns[columnPos[ID]].equals(""))
								|| (columnPos[NLIS]!=-1&&columnPos[NLIS]<columns.length&&!columns[columnPos[NLIS]].equals(""))
								|| (columnPos[RF]!=-1&&columnPos[RF]<columns.length&&!columns[columnPos[RF]].equals(""))){
							        for(Bovine b : getBovines()){
							        	if((columnPos[ID]!=-1&&columnPos[ID]<columns.length&&!columns[columnPos[ID]].equals("")&&b.getEartag().getEartagID()!=null&&lowerCase(b.getEartag().getEartagID()).equals(lowerCase(columns[columnPos[ID]])))
							        	|| (columnPos[NLIS]!=-1&&columnPos[NLIS]<columns.length&&!columns[columnPos[NLIS]].equals("")&&b.getEartag().getNlisID()!=null&&lowerCase(b.getEartag().getNlisID()).equals(lowerCase(columns[columnPos[NLIS]])))
							        	|| (columnPos[RF]!=-1&&columnPos[RF]<columns.length&&!columns[columnPos[RF]].equals("")&&b.getEartag().getRfID()!=null&lowerCase(b.getEartag().getRfID()).equals(lowerCase(columns[columnPos[RF]])))){
							        		if(b.isActive()){
							        			Eartag eartag = b.getEartag();
							        			String m="An animal with the same ID ("+"Visual: \""+eartag.getEartagID()+"\", NLIS: \""+eartag.getNlisID()+"\", RFID: \""+eartag.getRfID()+"\") is already active";
							        			log+="\n"+i+" : "+m;
							        			System.out.println(m);
							        			found=true;
							        			fail++;
							        		}
							        		break;
							        	}
							        }
								}
						        if(!found){
						        	// create new animal
						        	try {
										Bovine b = new Bovine(columnPos[DOB]==-1||columnPos[DOB]>=columns.length||columns[columnPos[DOB]].trim().equals("")?null:(Date) new SimpleDateFormat(dateFormat).parse(columns[columnPos[DOB]].replaceAll(".","/").replaceAll("-","/")));
										CattleBreed breed = LivestockSolutions.getCattleBreed(columnPos[BREED]>=columns.length?"Unknown Breed":columns[columnPos[BREED]].trim());
										if(breed==null){
											// Create a new Breed with this name.
											if(addCattleBreed(columnPos[BREED]>=columns.length?"Unknown Breed":columns[columnPos[BREED]].trim(), 0f, 0f, 0f, 0f, 0f, 0, 0, 0)){
												breed = LivestockSolutions.getCattleBreed(columns.length<=columnPos[BREED]?"Unknown Breed":columns[columnPos[BREED]].trim());
												String m="Breed \""+(columnPos[BREED]>=columns.length?"Unknown Breed":columns[columnPos[BREED]].trim())+"\" was created and added to the system with default values";
												log+="\n"+i+" : "+m;
												System.out.println(m);
												
											}else{
												String m="Breed \""+(columnPos[BREED]>=columns.length?"Unknown Breed":columns[columnPos[BREED]].trim())+"\" did not exist and could not be created";
												log+="\n"+i+" : "+m;
												System.out.println(m);
												fail++;
												break;
											}
										}
										b.setBreed(breed);
										
										Eartag eartag = new Eartag();
										if(columnPos[ID]!=-1&&columnPos[ID]<columns.length&&!columns[columnPos[ID]].equals("")){
											eartag.setEartagID(columns[columnPos[ID]].trim());
										}
										if(columnPos[NLIS]!=-1&&columnPos[NLIS]<columns.length&&!columns[columnPos[NLIS]].equals("")){
											eartag.setNlisID(columns[columnPos[NLIS]].trim());
										}
										if(columnPos[RF]!=-1&&columnPos[RF]<columns.length&&!columns[columnPos[RF]].equals("")){
											eartag.setRfID(columns[columnPos[RF]].trim());
										}
										b.setEartag(eartag);
										
										if(columnPos[GENDER]!=-1&&columnPos[GENDER]<columns.length&&!columns[columnPos[GENDER]].equals("")){
											String gend = lowerCase(columns[columnPos[GENDER]].substring(0,1));
											if(gend.equals("m")){
												b=b.setGender(Bull.class);
											}else if(gend.equals("f")){
												b=b.setGender(Cow.class);
											}
										}
										
										if(columnPos[SIRE]!=-1&&columnPos[SIRE]<columns.length&&!columns[columnPos[SIRE]].equals("")||
											columnPos[DAME]!=-1&&columnPos[DAME]<columns.length&&!columns[columnPos[DAME]].equals("")){
											for(Bovine elder : getBovines()){
												if(columnPos[SIRE]!=-1&&columnPos[SIRE]<columns.length&&!columns[columnPos[SIRE]].equals("") && elder instanceof Bull){
										        	if((elder.getEartag().getEartagID()!=null&&lowerCase(elder.getEartag().getEartagID()).equals(lowerCase(columns[columnPos[SIRE]])))
										        	|| (elder.getEartag().getNlisID()!=null&&lowerCase(elder.getEartag().getNlisID()).equals(lowerCase(columns[columnPos[SIRE]])))
										        	|| (elder.getEartag().getRfID()!=null&&lowerCase(elder.getEartag().getRfID()).equals(lowerCase(columns[columnPos[SIRE]])))){
										        		b.setSire((Bull)elder);
										        	}
												}
												
												if(columnPos[DAME]!=-1&&columnPos[DAME]<columns.length&&!columns[columnPos[DAME]].equals("") && elder instanceof Cow){
										        	if((elder.getEartag().getEartagID()!=null&&lowerCase(elder.getEartag().getEartagID()).equals(lowerCase(columns[columnPos[DAME]])))
										        	|| (elder.getEartag().getNlisID()!=null&&lowerCase(elder.getEartag().getNlisID()).equals(lowerCase(columns[columnPos[DAME]])))
										        	|| (elder.getEartag().getRfID()!=null&&lowerCase(elder.getEartag().getRfID()).equals(lowerCase(columns[columnPos[DAME]])))){
										        		b.setDame((Cow)elder);
										        	}
												}
									        }
										}
										
										float price = 0;
										Owner owner=null;
										String acquireMethod = "Birth";
										date=b.getDob();
										if(columnPos[PRICE]!=-1&&columnPos[PRICE]<columns.length&&!columns[columnPos[PRICE]].equals("")){
											try{
												price = Float.parseFloat(columns[columnPos[PRICE]]);
											}catch(Exception e){
												String m="Expecting a decimal value for Price field, but got \""+columns[columnPos[PRICE]]+"\" instead";
												log+="\n"+i+" : "+m;
												System.out.println(m);
												e.printStackTrace();
											}
										}
										
										if(columnPos[PREVIOUSOWNER]!=-1&&columnPos[PREVIOUSOWNER]<columns.length&&!columns[columnPos[PREVIOUSOWNER]].equals("")){
											boolean foundOwner = false;
											for(Owner o : getOwners()){
												if(lowerCase(o.getName()).equals(lowerCase(columns[columnPos[PREVIOUSOWNER]]))){
									        		owner=o;
									        		foundOwner=true;
									        		break;
									        	}
									        }
											
											if(!foundOwner){
												System.out.println("Adding a new Owner as the one listed currently does not exist: "+columns[columnPos[PREVIOUSOWNER]]);
												owner=new Owner();
												owner.setName(columns[columnPos[PREVIOUSOWNER]]);
												addOwner(owner);
											}
											acquireMethod="Purchase";
											date = new Date();
										}
										Acquisition a = new Acquisition(acquireMethod, price, owner, date);
										b.setAcquisition(a);
										
										if(columnPos[MOB]!=-1&&columnPos[MOB]<columns.length&&!columns[columnPos[MOB]].equals("")){
											Mob mob = null;
											boolean foundMob = false;
											for(Mob m : getOnlyMobs()){
									        	if(lowerCase(m.getName()).equals(lowerCase(columns[columnPos[MOB]]))){
									        		mob=m;
									        		foundMob=true;
									        		break;
									        	}
									        }
											if(!foundMob){
												mob=new Mob(columns[columnPos[MOB]].trim());
												addGroup(mob);
											}
											b.setMob(mob);
										}
										
										if(columnPos[GROUP]!=-1&&columnPos[GROUP]<columns.length&&!columns[columnPos[GROUP]].equals("")){
											Group group = null;
											boolean foundGroup = false;
											for(Group g : getOnlyGroups()){
									        	if(lowerCase(g.getName()).equals(lowerCase(columns[columnPos[GROUP]]))){
									        		group=g;
									        		foundGroup=true;
									        		break;
									        	}
									        }
											if(!foundGroup){
												group=new Group(columns[columnPos[GROUP]].trim());
												addGroup(group);
											}
											b.addGroup(group);
										}
										for(int ga=extras+1; ga<columns.length;ga++){
											if(!columns[ga].equals("")){
												Group group = null;
												boolean foundGroup = false;
												for(Group g : getOnlyGroups()){
										        	if(lowerCase(g.getName()).equals(lowerCase(columns[ga]))){
										        		group=g;
										        		foundGroup=true;
										        		break;
										        	}
										        }
												if(!foundGroup){
													group=new Group(columns[ga].trim());
													addGroup(group);
												}
												b.addGroup(group);
											}
										}
										added++;
										String m="Added: "+strLine;
										log+="\n"+i+" : "+m;
										System.out.println(m);
										addBovine(b);
						        	}catch (Exception e){
						        		String m="Error processing line, "+strLine;
						        		log+="\n"+i+" : "+m;
							        	System.out.println(m);
							        	fail++;
										e.printStackTrace();
									}
						        }
							}
							weight = false;
						}else{
							// Could be a weight csv
							weight = true;
							
							if(columnPos==null){
								for(String c : columns){
									try{
										Double.parseDouble(c);
										// Column row header must be the previous line;
										columnPos = new int[]{-1,-1};
										String[] columnHeader = lowerCase(previousLine).split(",");
										for(int x=0; x<columnHeader.length; x++){
											if(columnHeader[x].contains("id")){
												columnPos[ID]=x;
												System.out.println("Found ID column: "+x);
											}else if(columnHeader[x].contains("weight")){
												if(columnPos[WEIGHT]==-1){
													columnPos[WEIGHT]=x;
													System.out.println("Found Weight column: "+x);
												}else{
													// Work out which field matches "weight" most.
													if(columnHeader[x].equals("weight") && !columnHeader[columnPos[WEIGHT]].equals("weight")){
														columnPos[WEIGHT]=x;
														System.out.println("Found Weight column: "+x);
													}
												}
											}
										}
										/* Assume that if we could not find the fields, id will be the first column and
										 * weight will be the second.
										 */
										if(columnPos[ID]==-1) {
											columnPos[ID]=0;
										}
										if(columnPos[WEIGHT]==-1) {
											columnPos[WEIGHT]=1;
										}
									}catch(Exception e){
										// Not a number field.
									}
								}
							}else{
								boolean found = false;
						        for(Bovine b : getBovines()){
						        	if((b.getEartag().getEartagID()!=null&&lowerCase(b.getEartag().getEartagID()).equals(lowerCase(columns[columnPos[ID]])))
						        	|| (b.getEartag().getNlisID()!=null&&lowerCase(b.getEartag().getNlisID()).equals(lowerCase(columns[columnPos[ID]])))
						        	|| (b.getEartag().getRfID()!=null&&lowerCase(b.getEartag().getRfID()).equals(lowerCase(columns[columnPos[ID]])))){
						        		b.addWeighing(new Weight(date,Double.parseDouble(columns[columnPos[WEIGHT]])));
						        		String m="Added weighing for Animal with ID: "+ columns[columnPos[ID]] +", weight: "+ Double.parseDouble(columns[columnPos[WEIGHT]]) +", Date: "+ new SimpleDateFormat(dateFormat).format(date);
						        		log+="\n"+i+" : "+m;
						        		System.out.println(m);
						        		found=true;
						        		added++;
						        		break;
						        	}
						        }
						        if(!found){
						        	String m="Could not find Animal with ID: "+ columns[columnPos[ID]] +" to apply the weight: "+ Double.parseDouble(columns[columnPos[WEIGHT]]) +" for the date of "+ new SimpleDateFormat(dateFormat).format(date);
						        	log+="\n"+i+" : "+m;
						        	System.out.println(m);
						        	fail++;
						        }
							}
						}
					}
					previousLine=strLine;
				}
				message="Reading line "+ i;
				progress=offset+(int) ((i/(double)length*100)*scale);
				i++;
			}
			br.close();
		} catch (FileNotFoundException e) {
			message="File could not be found";
			System.out.println(message);
			progress=-1;
			return false;
		} catch (IOException e) {
			message="File could not be read";
			System.out.println(message);
			e.printStackTrace();
			progress=-1;
			return false;
		}
		message="Finished importing data. "+added+" Item"+(added!=1?"s":"")+" imported, "+fail+" Item"+(fail!=1?"s":"")+" failed.";
		System.out.println(message);
		progress=offset+(int) (101*scale);
		return true;
	}
	
	/**
	 * Sync between this system and the android device.
	 * @return <code>true</code> if synchronisation was successful.
	 * @see #syncBetweenDevice(String)
	 */
	public static boolean syncBetweenDevice(){
		return syncBetweenDevice(lastSyncPath);
	}
	/**
	 * Sync between this system and the android device.
	 * @param syncPath The path which defines the Android device.
	 * @return <code>true</code> if synchronisation was successful
	 */
	public static boolean syncBetweenDevice(String syncPath){
		message="";
		progress=offset+(int) (0*scale);
		lastSyncPath=syncPath;
		System.out.println("Starting Synchronisation");

		if(lastSyncPath==null){
			// Ask where the Android device is mounted to.
			message="Please choose the directory where the Android device is mounted at.";
			return false;
		}
		File path = new File(lastSyncPath);
		if(path.isFile()){
			path=path.getParentFile();
			lastSyncPath=path.getAbsolutePath();
		}
		boolean found=false;
		while(path!=null && path.listFiles()!=null){
			for(File f : path.listFiles()){
				if(f.getName().equals("Livestock Solutions")){
					found=true;
					break;
				}
			}
			if(found){
				break;
			}
			path=path.getParentFile();
		}

		if(found){
			lastSyncPath=path.getAbsolutePath();
		}
		File androidPath = new File(lastSyncPath);
		progress=offset+(int) (5*scale);
		HashMap<Long,CattleBreed> tcattleBreeds = LivestockSolutions.cattleBreeds;
		HashMap<Long,Bovine> tcattle 			= LivestockSolutions.cattle;
		HashMap<Long,Group> tgroups 			= LivestockSolutions.groups;
		HashMap<Long,Join> tjoinings 			= LivestockSolutions.joinings;
		HashMap<Long,Location> tlocations 		= LivestockSolutions.locations;
		HashMap<Long,Owner> towners 			= LivestockSolutions.owners;
		HashMap<Long,UsableItem<? extends UsedItem>> tusableItems 	= LivestockSolutions.usableItems;
		HashMap<Long,Observation> tobservations = LivestockSolutions.observations;
		
		HashMap<Long,CattleBreed> cattleBreeds;
		HashMap<Long,Bovine> cattle;
		HashMap<Long,Group> groups;
		HashMap<Long,Join> joinings;
		HashMap<Long,Location> locations;
		HashMap<Long,Owner> owners;
		HashMap<Long,UsableItem<? extends UsedItem>> usableItems;
		HashMap<Long,Observation> observations;
		
		// Try to find the file
		File androidDataFile;
		androidDataFile = new File(androidPath,APPDIR+File.separator+DATAFILE);
		System.out.println("  Loading file: "+androidDataFile.getAbsolutePath());
		progress=offset+(int) (10*scale);
		offset=10;
		scale=0.2;
		try{
			if(load(androidDataFile)||androidPath.exists()){
				lastSyncPath=androidPath.getAbsolutePath();
				progress=offset+(int) (20*scale);
				offset=0;
				scale=1;
	
				cattleBreeds 						= LivestockSolutions.cattleBreeds;
				LivestockSolutions.cattleBreeds		= tcattleBreeds;
				cattleBreeds=cattleBreeds==LivestockSolutions.cattleBreeds?new HashMap<Long,CattleBreed>():cattleBreeds;
				
				cattle 								= LivestockSolutions.cattle;
				LivestockSolutions.cattle 			= tcattle;
				cattle=cattle==LivestockSolutions.cattle?new HashMap<Long,Bovine>():cattle;
				
				groups 								= LivestockSolutions.groups;
				LivestockSolutions.groups 			= tgroups;
				groups=groups==LivestockSolutions.groups?new HashMap<Long,Group>():groups;
				
				joinings 							= LivestockSolutions.joinings;
				LivestockSolutions.joinings 		= tjoinings;
				joinings=joinings==LivestockSolutions.joinings?new HashMap<Long,Join>():joinings;
				
				locations 							= LivestockSolutions.locations;
				LivestockSolutions.locations 		= tlocations;
				locations=locations==LivestockSolutions.locations?new HashMap<Long,Location>():locations;
				
				owners 								= LivestockSolutions.owners;
				LivestockSolutions.owners 			= towners;
				owners=owners==LivestockSolutions.owners?new HashMap<Long,Owner>():owners;
				
				usableItems 						= LivestockSolutions.usableItems;
				LivestockSolutions.usableItems 		= tusableItems;
				usableItems=usableItems==LivestockSolutions.usableItems?new HashMap<Long,UsableItem<? extends UsedItem>>():usableItems;
				
				observations 						= LivestockSolutions.observations;
				LivestockSolutions.observations 	= tobservations;
				observations=observations==LivestockSolutions.observations?new HashMap<Long,Observation>():observations;
				progress=offset+(int) (22*scale);
				// Merge the Changed data
				for(CattleBreed cb : LivestockSolutions.cattleBreeds.values()){
					cb.update(cattleBreeds.get(cb.getID()));
					cattleBreeds.remove(cb.getID());
				}
				LivestockSolutions.cattleBreeds.putAll(cattleBreeds);
				progress=offset+(int) (28*scale);
				for(Bovine b : LivestockSolutions.cattle.values()){
					b.update(cattle.get(b.getID()));
					cattle.remove(b.getID());
				}
				LivestockSolutions.cattle.putAll(cattle);
				progress=offset+(int) (40*scale);
				for(Group g : LivestockSolutions.groups.values()){
					g.update(groups.get(g.getID()));
					groups.remove(g.getID());
				}
				LivestockSolutions.groups.putAll(groups);
				progress=offset+(int) (47*scale);
				for(Join j : LivestockSolutions.joinings.values()){
					j.update(joinings.get(j.getID()));
					joinings.remove(j.getID());
				}
				LivestockSolutions.joinings.putAll(joinings);
				progress=offset+(int) (54*scale);
				for(Location l : LivestockSolutions.locations.values()){
					l.update(locations.get(l.getID()));
					locations.remove(l.getID());
				}
				LivestockSolutions.locations.putAll(locations);
				progress=offset+(int) (60*scale);
				for(Owner o : LivestockSolutions.owners.values()){
					o.update(owners.get(o.getID()));
					owners.remove(o.getID());
				}
				LivestockSolutions.owners.putAll(owners);
				progress=offset+(int) (68*scale);
				for(UsableItem<? extends UsedItem> u : LivestockSolutions.usableItems.values()){
					u.update(usableItems.get(u.getID()));
					usableItems.remove(u.getID());
				}
				LivestockSolutions.usableItems.putAll(usableItems);
				progress=offset+(int) (73*scale);
				for(Observation o : LivestockSolutions.observations.values()){
					o.update(observations.get(o.getID()));
					observations.remove(o.getID());
				}
				LivestockSolutions.observations.putAll(observations);
				progress=offset+(int) (80*scale);
				offset=80;
				scale=0.1;
				save();
				offset=0;
				scale=1;
				progress=offset+(int) (90*scale);
				offset=90;
				scale=0.1;
				androidDataFile.getParentFile().mkdirs();
				if(save(androidDataFile)){
					offset=0;
					scale=1;
					System.out.println("Successfull Sync to device");
					message="";
					progress=offset+(int) (101*scale);
					saveSettings();
					return true;
				}
				message="Could not Update Device data: "+message;
			}
		}catch(Exception e){
			System.out.println("  Synchronisation Failed: ");
			message=e.getMessage()!=null?e.getMessage():"There was a problem during the synchronisation process";
			e.printStackTrace();
			
			// Make sure the temporary stored data is copied back in an attempt to preserve data and not lose any.
			LivestockSolutions.cattleBreeds		= tcattleBreeds;
			LivestockSolutions.cattle 			= tcattle;
			LivestockSolutions.groups 			= tgroups;
			LivestockSolutions.joinings 		= tjoinings;
			LivestockSolutions.locations 		= tlocations;
			LivestockSolutions.owners 			= towners;
			LivestockSolutions.usableItems 		= tusableItems;
			LivestockSolutions.observations 	= tobservations;
		}
		progress=offset+(int) (-1*scale);
		System.out.println("Failed to Sync to device");
		if(message==""){
			message="Could not read data from Android device";
		}
		return false;
	}
	
	/**
	 * Saves to the provided file, a serialised version 
	 * @return <code>true</code> if file was saved with no errors
	 * @see #save(File)
	 */
	public static boolean save(){
		return save(new File(getStorageLocation(),DATAFILE));
	}
	
	/**
	 * Saves to the provided file, a serialised version 
	 * @param file File to save to
	 * @return <code>true</code> if file was saved with no errors
	 */
	public static boolean save(File file){
		progress=offset+(int) (0*scale);
		ObjectOutputStream oos = null;
		try{
			message="Preparing to Save Data...";
			FileOutputStream fos = new FileOutputStream(file);
			oos = new ObjectOutputStream((fos));
			oos.writeObject(DATAFILEIDENT+":"+DATAFILEVERSION);

			oos = new ObjectOutputStream(new GZIPOutputStream(fos));
			Gson gson = new Gson();
			message="Saving Data (Cattle Breeds...)";
			oos.writeObject(gson.toJson(cattleBreeds,new TypeToken<HashMap<Long,CattleBreed>>() {}.getType()));
			
			// Split Cattle HashMap into Bovine, Cow and Bull to keep these types
			message="Saving Data (Cattle...)";
			HashMap<Long,Bovine> bovine = new HashMap<Long,Bovine>();
			HashMap<Long,Cow> cow = new HashMap<Long,Cow>();
			HashMap<Long,Bull> bull = new HashMap<Long,Bull>();
			for(Bovine b : cattle.values()){
				if(b instanceof Cow){
					cow.put(b.getID(), (Cow)b);
				}else if(b instanceof Bull){
					bull.put(b.getID(), (Bull)b);
				}else{
					bovine.put(b.getID(), b);
				}
			}
			oos.writeObject(gson.toJson(bovine,new TypeToken<HashMap<Long,Bovine>>() {}.getType()));
			oos.writeObject(gson.toJson(bull,new TypeToken<HashMap<Long,Bull>>() {}.getType()));
			oos.writeObject(gson.toJson(cow,new TypeToken<HashMap<Long,Cow>>() {}.getType()));
			progress=offset+(int) (10*scale);
			// Split Groups HashMap into Group and Mob to keep these types
			message="Saving Data (Groups/Mobs...)";
			HashMap<Long,Group> group = new HashMap<Long,Group>();
			HashMap<Long,Mob> mob = new HashMap<Long,Mob>();
			for(Group g : groups.values()){
				if(g instanceof Mob){
					mob.put(g.getID(), (Mob)g);
				}else{
					group.put(g.getID(), g);
				}
			}
			oos.writeObject(gson.toJson(group,new TypeToken<HashMap<Long,Group>>() {}.getType()));
			oos.writeObject(gson.toJson(mob,new TypeToken<HashMap<Long,Mob>>() {}.getType()));
			
			oos.writeObject(gson.toJson(joinings,new TypeToken<HashMap<Long,Join>>() {}.getType()));
			progress=offset+(int) (20*scale);
			// Split Location HashMap into Location and Paddocks to keep these types
			message="Saving Data (Locations/Paddocks...)";
			HashMap<Long,Location> location = new HashMap<Long,Location>();
			HashMap<Long,Paddock> paddock = new HashMap<Long,Paddock>();
			for(Location l : locations.values()){
				if(l instanceof Paddock){
					paddock.put(l.getID(), (Paddock)l);
				}else{
					location.put(l.getID(), l);
				}
			}
			oos.writeObject(gson.toJson(location,new TypeToken<HashMap<Long,Location>>() {}.getType()));
			oos.writeObject(gson.toJson(paddock,new TypeToken<HashMap<Long,Paddock>>() {}.getType()));
			progress=offset+(int) (40*scale);
			// Split Owners HashMap into Owner, Business and Farm to keep these types
			message="Saving Data (Contacts...)";
			HashMap<Long,Owner> owner = new HashMap<Long,Owner>();
			HashMap<Long,Business> business = new HashMap<Long,Business>();
			HashMap<Long,Farm> farm = new HashMap<Long,Farm>();
			for(Owner o : owners.values()){
				if(o instanceof Farm){
					farm.put(o.getID(), (Farm)o);
				}else if(o instanceof Business){
					business.put(o.getID(), (Business)o);
				}else{
					owner.put(o.getID(), o);
				}
			}
			oos.writeObject(gson.toJson(owner,new TypeToken<HashMap<Long,Owner>>() {}.getType()));
			oos.writeObject(gson.toJson(business,new TypeToken<HashMap<Long,Business>>() {}.getType()));
			oos.writeObject(gson.toJson(farm,new TypeToken<HashMap<Long,Farm>>() {}.getType()));
			progress=offset+(int) (70*scale);
			// Split UsableItem HashMap into Feed, Chemical and Medicine to keep these types
			message="Saving Data (Items...)";
			HashMap<Long,Feed> food = new HashMap<Long,Feed>();
			HashMap<Long,Chemical> chemical = new HashMap<Long,Chemical>();
			HashMap<Long,Medicine> medicine = new HashMap<Long,Medicine>();
			HashMap<Long,UsableItem<? extends UsedItem>> usableItem = new HashMap<Long,UsableItem<? extends UsedItem>>();
			for(UsableItem<? extends UsedItem> u : usableItems.values()){
				if(u instanceof Feed){
					food.put(u.getID(), (Feed)u);
				}else if(u instanceof Chemical){
					chemical.put(u.getID(), (Chemical)u);
				}else if(u instanceof Medicine){
					medicine.put(u.getID(), (Medicine)u);
				}else{
					usableItem.put(u.getID(),u);
				}
			}
			oos.writeObject(gson.toJson(usableItem,new TypeToken<HashMap<Long,UsableItem<? extends UsedItem>>>() {}.getType()));
			oos.writeObject(gson.toJson(food,new TypeToken<HashMap<Long,Feed>>() {}.getType()));
			oos.writeObject(gson.toJson(chemical,new TypeToken<HashMap<Long,Chemical>>() {}.getType()));
			oos.writeObject(gson.toJson(medicine,new TypeToken<HashMap<Long,Medicine>>() {}.getType()));
			progress=offset+(int) (88*scale);
			message="Saving Data (Observations...)";
			oos.writeObject(gson.toJson(observations,new TypeToken<HashMap<Long,Observation>>() {}.getType()));
			
			progress=offset+(int) (98*scale);
			scale=0.2;
			offset=98;
			saveSettings();
			offset=0;
			scale=1;
			lastModified= file.lastModified();
			progress=offset+(int) (101*scale);
			message="Saved Data";
			return true;
		}catch(FileNotFoundException e){
			e.printStackTrace();
			message=e.getMessage();
			progress=-1;
		} catch (IOException e) {
			e.printStackTrace();
			message="An internal error occured (S01IOE)";
			progress=-1;
		} finally {
			if(oos!=null){
				try {
					oos.close();
				} catch (IOException e) {}
			}
		}
		return false;
	}
	
	/**
	 * Saves the settings file to a predetermined location.
	 * @return <tt>true</tt> if saving was a success, <tt>false</tt> otherwise.
	 */
	protected static boolean saveSettings(){
		progress=offset+(int) (0*scale);
		message="Saving Settings...";
		ObjectOutputStream oos = null;
		try{
			oos = new ObjectOutputStream(new GZIPOutputStream(new FileOutputStream(new File(getStorageLocation(),SETTINGS))));
			oos.writeObject(backupPath);
			oos.writeObject(backupPeriodInDays);
			oos.writeObject(lastBackup);
			oos.writeObject(lastSyncPath);
			
			// Write the date format set for dates for this application to use.
			oos.writeObject(dateFormat);
			oos.writeObject(colourSettings);
			oos.writeObject(programSettings);
			
			progress=offset+(int) (101*scale);
			message="Saved Settings";
			return true;
		}catch(Exception e){
			e.printStackTrace();
			message="Failed to Save Settings";
			progress=-1;
			return false;
		}finally{
			if(oos!=null){
				try {
					oos.close();
				} catch (IOException e) {}
			}
		}
	}
	
	/**
	 * Loads a file assuming correct format
	 * @return <code>true</code> if file was loaded with no errors
	 * @see #load(File)
	 */
	public static boolean load(){
		return load(new File(getStorageLocation(),DATAFILE));
	}
	
	/**
	 * Loads a file assuming correct format
	 * @param location The File to load
	 * @return <code>true</code> if file was loaded with no errors
	 */
	public static boolean load(File location){
		if(location.exists()){
			ObjectInputStream ois = null;
			try{
				message="Loading File...";
				FileInputStream fis = new FileInputStream(location);
				ois = new ObjectInputStream(fis);
				progress=offset+(int) (0*scale);
				// Read the File identity and determine if data file is a newer version than what is supported.
				message="Reading File Version...";
				String dataIdentity = (String)ois.readObject();
				String[] s = dataIdentity.split(":");
				double dataVersion = s.length>1?Double.valueOf(s[s.length-1]):0.00;
				dataIdentity= dataIdentity.replaceAll(":"+dataVersion,"");
				if(dataVersion>DATAFILEVERSION){
					// Data file was created from a newer version, saving data to the file may cause data to be lost.
					// Do not allow loading of a newer file to prevent data loss.
					message="The data file was created using a newer version (File version: "+dataVersion+"), however this version requires a file version of "+DATAFILEVERSION+" or less to prevent possible data loss.\nPlease check for a newer version";
					return false;
				}
				if(dataIdentity.contains(DATAFILEIDENT)){
					message="Loading Data...";
					progress=offset+(int) (10*scale);
					ois = new ObjectInputStream(new GZIPInputStream(fis));
					Gson gson = new Gson();
					message="Loading Data (Cattle Breeds...)";
					cattleBreeds= (HashMap<Long,CattleBreed>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,CattleBreed>>() {}.getType());
					progress=offset+(int) (15*scale);
					message="Loading Data (Cattle...)";
					cattle 		= (HashMap<Long,Bovine>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Bovine>>() {}.getType());
					progress=offset+(int) (20*scale);
					cattle.putAll((HashMap<Long,Bovine>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Bull>>() {}.getType()));
					progress=offset+(int) (25*scale);
					cattle.putAll((HashMap<Long,Bovine>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Cow>>() {}.getType()));
					progress=offset+(int) (30*scale);
					message="Loading Data (Groups/Mobs...)";
					groups 		= (HashMap<Long,Group>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Group>>() {}.getType());
					progress=offset+(int) (35*scale);
					groups.putAll((HashMap<Long,Group>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Mob>>() {}.getType()));
					progress=offset+(int) (40*scale);
					message="Loading Data (Joinings...)";
					joinings 	= (HashMap<Long,Join>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Join>>() {}.getType());
					progress=offset+(int) (45*scale);
					message="Loading Data (Locations/Paddocks...)";
					locations 	= (HashMap<Long,Location>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Location>>() {}.getType());
					progress=offset+(int) (50*scale);
					locations.putAll((HashMap<Long,Location>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Paddock>>() {}.getType()));
					progress=offset+(int) (53*scale);
					message="Loading Data (Contacts...)";
					owners 		= (HashMap<Long,Owner>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Owner>>() {}.getType());
					progress=offset+(int) (60*scale);
					owners.putAll((HashMap<Long,Owner>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Business>>() {}.getType()));
					progress=offset+(int) (65*scale);
					owners.putAll((HashMap<Long,Owner>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Farm>>() {}.getType()));
					progress=offset+(int) (70*scale);
					message="Loading Data (Items...)";
					usableItems	= (HashMap<Long,UsableItem<? extends UsedItem>>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,UsableItem<? extends UsedItem>>>() {}.getType());
					progress=offset+(int) (75*scale);
					usableItems.putAll((HashMap<Long,Feed>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Feed>>() {}.getType()));
					progress=offset+(int) (80*scale);
					usableItems.putAll((HashMap<Long,Chemical>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Chemical>>() {}.getType()));
					progress=offset+(int) (85*scale);
					usableItems.putAll((HashMap<Long,Medicine>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Medicine>>() {}.getType()));
					progress=offset+(int) (90*scale);
					message="Loading Data (Observations...)";
					observations= (HashMap<Long,Observation>)gson.fromJson(((String)ois.readObject()), new TypeToken<HashMap<Long,Observation>>() {}.getType());
					progress=offset+(int) (100*scale);
					lastModified= location.lastModified();
					progress=offset+(int) (101*scale);
					message="Loaded Data";
					return true;
				}
				message="This File does not appear to be a valid Livestock Solutions data file";
			}catch(FileNotFoundException e){
				e.printStackTrace();
				save();
			} catch (IOException e) {
				e.printStackTrace();
				message="This File does not appear to be a valid Livestock Solutions data file";
			} catch (JsonSyntaxException e) {
				e.printStackTrace();
				message="An internal error occured (L01JSE)";
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
				message="An internal error occured (L01CNF)";
			} catch (Exception e){
				e.printStackTrace();
				message="An error occurred while trying to load the data";
			} finally {

				if(ois!=null){
					try {
						ois.close();
					} catch (IOException e) {}
				}
			}
		}else{
			message="No file found at path";
		}
		return false;
	}
	
	/**
	 * Loads the settings file from a predetermined location.
	 * @return <tt>true</tt> if loading was a success, <tt>false</tt> otherwise.
	 */
	protected static boolean loadSettings(){
		progress=offset+(int) (0*scale);
		ObjectInputStream ois = null;
		BufferedInputStream bis = null;
		message="Loading Settings...";
		try{
			bis = new BufferedInputStream(new GZIPInputStream(new FileInputStream(new File(getStorageLocation(),SETTINGS))));
			ois = new ObjectInputStream(bis);
			backupPath=(String) ois.readObject();
			backupPeriodInDays=(Integer) ois.readObject();
			lastBackup=(Long) ois.readObject();
			lastSyncPath=(String) ois.readObject();
			
			// Additional attributes that may not already exist in the file.
			if(bis.available()>0){
				dateFormat=(String) ois.readObject();
				colourSettings=(HashMap<Integer,String>)ois.readObject();
				
				if(bis.available()>0){
					programSettings=(HashMap<String,Object>)ois.readObject();
				}
			}
			
			progress=offset+(int) (101*scale);
			message="Loaded Settings";
			return true;
		}catch(Exception e){
			saveSettings();
			progress=-1;
			message="Failed to Load Settings";
			return false;
		} finally{
			if(ois!=null){
				try {
					ois.close();
				} catch (IOException e) {}
			}
			if(bis!=null){
				try {
					bis.close();
				} catch (IOException e) {}
			}
		}
	}
	
	/**
	 * Return a list of the Bovines currently in the System.<br>
	 * The list will exclude any Bovine that has been Archived.
	 * @return A list of Bovine Objects.
	 */
	public static Bovine[] getBovines(){
		return cattle.values().toArray(new Bovine[cattle.size()]);
	}
	
	/**
	 * Return a list of the Bovines currently in the System that are only active.<br>
	 * @return A list of Bovine Objects.
	 */
	public static Bovine[] getActiveBovines(){
		ArrayList<Bovine> bArray = new ArrayList<Bovine>();
		for(Bovine b : cattle.values()){
			if(b.isActive()){
				bArray.add(b);
			}
		}
		return bArray.toArray(new Bovine[bArray.size()]);
	}
	
	/**
	 * Return a list of the Joinings currently in the System.<br>
	 * @return A list of Joining Objects.
	 */
	public static Join[] getJoinings(){
		return joinings.values().toArray(new Join[joinings.size()]);
	}
	
	/**
	 * Return a list of the Groups currently in the System.<br>
	 * @return A list of Group Objects, this will include both Mob and Group Objects.
	 */
	public static Group[] getGroups(){
		return groups.values().toArray(new Group[groups.size()]);
	}
	
	/**
	 * Return a list of Only Group Objects.
	 * @return A list of Group Objects. Only Group Objects.
	 */
	public static Group[] getOnlyGroups(){
		ArrayList<Group> list = new ArrayList<Group>();
		for(Group a: groups.values()){
			if(!(a instanceof Mob)){
				list.add(a);
			}
		}
		return list.toArray(new Group[list.size()]);
	}
	
	/**
	 * Return a list of Only Mob Objects.
	 * @return A list of Mob Objects.
	 */
	public static Mob[] getOnlyMobs(){
		ArrayList<Mob> list = new ArrayList<Mob>();
		for(Group a: groups.values()){
			if(a instanceof Mob){
				list.add((Mob)a);
			}
		}
		return list.toArray(new Mob[list.size()]);
	}
	
	/**
	 * Return a list of Locations and Paddocks that are currently stored in the System.
	 * @return An array of Location objects, which include Paddock Objects too.
	 */
	public static Location[] getLocations(){
		return locations.values().toArray(new Location[locations.size()]);
	}
	
	/**
	 * Return a list of Only Location Objects.
	 * @return A list of Location Objects. Only Location Objects.
	 */
	public static Location[] getOnlyLocations(){
		ArrayList<Location> list = new ArrayList<Location>();
		for(Location a: locations.values()){
			if(!(a instanceof Paddock)){
				list.add(a);
			}
		}
		return list.toArray(new Location[list.size()]);
	}
	
	/**
	 * Return a list of Only Paddock Objects.
	 * @return A list of Paddock Objects.
	 */
	public static Paddock[] getOnlyPaddocks(){
		ArrayList<Paddock> list = new ArrayList<Paddock>();
		for(Location a: locations.values()){
			if(a instanceof Paddock){
				list.add((Paddock)a);
			}
		}
		return list.toArray(new Paddock[]{});
	}
	
	/**
	 * Return a list of Owner Objects that are currently stored in the System.
	 * @return An array of Owner objects.
	 */
	public static Owner[] getOwners(){
		return owners.values().toArray(new Owner[owners.size()]);
	}
	
	/**
	 * Return a list of Only Business Objects.
	 * @return A list of Business Objects.
	 */
	public static Business[] getOnlyBusinesses(){
		ArrayList<Business> list = new ArrayList<Business>();
		for(Owner a: owners.values()){
			if(a instanceof Business){
				list.add((Business)a);
			}
		}
		return list.toArray(new Business[]{});
	}
	
	/**
	 * Return a list of Only Farm Objects.
	 * @return A list of Farm Objects.
	 */
	public static Farm[] getOnlyFarms(){
		ArrayList<Farm> list = new ArrayList<Farm>();
		for(Owner a: owners.values()){
			if(a instanceof Farm){
				list.add((Farm)a);
			}
		}
		return list.toArray(new Farm[]{});
	}
	
	/**
	 * Return a list of UsableItem, including Feed, Chemical and Medicine Objects
	 * that are currently stored in the System.
	 * @return An array of UsableItem objects which may include sub classes such
	 * as Feed, Chemical and Medicine.
	 */
	public static UsableItem<? extends UsedItem>[] getUsableItems(){
		return usableItems.values().toArray(new UsableItem<?>[usableItems.size()]);
	}
	
	/**
	 * Return a list of Only Feed Objects.
	 * @return A list of Feed Objects.
	 */
	public static Feed[] getOnlyFeeds(){
		ArrayList<Feed> list = new ArrayList<Feed>();
		for(UsableItem<? extends UsedItem> a: usableItems.values()){
			if(a instanceof Feed){
				list.add((Feed)a);
			}
		}
		return list.toArray(new Feed[list.size()]);
	}
	
	/**
	 * Return a list of Only Chemical Objects.
	 * @return A list of Chemical Objects.
	 */
	public static Chemical[] getOnlyChemicals(){
		ArrayList<Chemical> list = new ArrayList<Chemical>();
		for(UsableItem<? extends UsedItem> a: usableItems.values()){
			if(a instanceof Chemical){
				list.add((Chemical)a);
			}
		}
		return list.toArray(new Chemical[list.size()]);
	}
	
	/**
	 * Return a list of Only Medicine Objects.
	 * @return A list of Medicine Objects.
	 */
	public static Medicine[] getOnlyMedicines(){
		ArrayList<Medicine> list = new ArrayList<Medicine>();
		for(UsableItem<? extends UsedItem> a: usableItems.values()){
			if(a instanceof Medicine){
				list.add((Medicine)a);
			}
		}
		return list.toArray(new Medicine[list.size()]);
	}
	
	/**
	 * Return a list of CattleBreed Objects.
	 * @return An array of CattleBreed objects.
	 */
	public static CattleBreed[] getBreeds(){
		return cattleBreeds.values().toArray(new CattleBreed[cattleBreeds.size()]);
	}
	
	/**
	 * Return a list of the Observations currently in the System.<br>
	 * @return A list of Observation Objects.
	 */
	public static Observation[] getObservations(){
		return observations.values().toArray(new Observation[observations.size()]);
	}
	
	/**
	 * Get the storage location for which the settings will be saved to as well as the
	 * active working directory.<br>
	 * The location of this on Windows is usually <code>C:\Users\&lt;USER&gt;\AppData\roaming\Livestock Solutions\</code>
	 * @return The File to this location.
	 */
	public static File getStorageLocation(){
		if(storagePath == null){
			String userHome = System.getProperty("user.home", ".");
			
			String osName = lowerCase(System.getProperty("os.name"));
			if (osName.contains("win")){
				String applicationData = System.getenv("APPDATA");
				storagePath = new File((applicationData != null?applicationData:userHome), APPDIR + File.separator);
			}else if(osName.contains("mac")){
				storagePath = new File(userHome, "Library/Application Support/" + APPDIR + File.separator);
			}else{
				storagePath = new File(userHome, APPDIR + File.separator);
			}
			storagePath.mkdirs();
		}
		return storagePath;
	}
	
	/**
	 * Set the storage path when the code determined path is not suitable
	 * @param location The new location to save items to.
	 */
	public static void setStorageLocation(File location){
		storagePath = location;
	}
	
	/**
	 * Deletes the storage files and unloads any data currently shown.
	 * <b>Warning:</b> Use only when needed, as once this is done it can not
	 * be reversed.
	 */
	public static void deleteAll(){
		cattleBreeds= new HashMap<Long,CattleBreed>();
		cattle 		= new HashMap<Long,Bovine>();
		groups 		= new HashMap<Long,Group>();
		joinings 	= new HashMap<Long,Join>();
		locations 	= new HashMap<Long,Location>();
		owners 		= new HashMap<Long,Owner>();
		usableItems	= new HashMap<Long,UsableItem<? extends UsedItem>>();
		observations= new HashMap<Long,Observation>();
		
		try{
			new File(getStorageLocation(),DATAFILE).delete();
			new File(getStorageLocation(),ARCHIVE).delete();
			new File(getStorageLocation(),SETTINGS).delete();
		
			getStorageLocation().delete();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	/**
	 * A wait that will crate a thread that will wait for five seconds before saving the file,
	 * if the time is at least 5 seconds from last use
	 */
	protected static void saveWait(){
		Calendar c = Calendar.getInstance();
		c.add(Calendar.SECOND, 1);
		lastSave=c.getTime().getTime();
		if(!threadActive){
		threadActive=true;
			new Thread(){
				@Override
				public void run(){
					
					while(new Date(lastSave).after(new Date())){
						try{
							sleep(5000);
						}catch(Exception e){}
					}
					threadActive=false;
					save();
				}
			}.start();
		}
	}
	
	/**
	 * Count the number of lines in this text file.
	 * (http://stackoverflow.com/questions/453018/number-of-lines-in-a-file-in-java)
	 * @param filename The text file to count the number of lines.
	 * @return The number of lines in the text file.
	 * @throws IOException
	 */
	public static int count(String filename) throws IOException {
	    InputStream is = new BufferedInputStream(new FileInputStream(filename));
	    try {
	        byte[] c = new byte[1024];
	        int count = 0;
	        int readChars = 0;
	        boolean empty = true;
	        while ((readChars = is.read(c)) != -1) {
	            empty = false;
	            for (int i = 0; i < readChars; ++i) {
	                if (c[i] == '\n')
	                    ++count;
	            }
	        }
	        return (count == 0 && !empty) ? 1 : count;
	    } finally {
	        is.close();
	    }
	}

	/**
	 * Get the last message that was set. The string value is set to empty ("") after this method is called.
	 * @return A String that is either empty or contains characters that can be shown to the user.
	 */
	public static String getMessage() {
		String m = message;
		message="";
		return m;
	}
	
	/**
	 * Get the last log that was set.<br>
	 * NOTE: Text in this field may be old text depending on previous activities.
	 * @return A String that is either empty or contains characters that can be shown to the user.
	 */
	public static String getLog(){
		return log;
	}
	
	/**
	 * Set the format of accepted dates.
	 * @param pattern A pattern such as "dd/MM/yy" that will format a date into a human readable form.
	 */
	public static void setDateFormat(String pattern){
		dateFormat=pattern;
		saveSettings();
	}
	
	/**
	 * Get the pattern a formatted date will be accepted as.
	 * @return The pattern applied to dates to make them readable.
	 */
	public static String getDateFormat(){
		return dateFormat;
	}
	
	/**
	 * Get the colours to colour the List in.
	 * @param colorType The part of the List to get the Colour for.
	 * @return The Colour for that portion of the list.
	 * 
	 * @see #HEADER
	 * @see #HEADER_ORDERED
	 * @see #LIST_A1
	 * @see #LIST_B1
	 * @see #LIST_A2
	 * @see #LIST_B2
	 * @see #LIST_HOVER
	 * @see #LIST_CLICK
	 */
	public static String getColour(int colorType){
		// If the value has been customised, use that colour.
		String colour=colourSettings.get(colorType);
		if(colour!=null){
			return colour;
		}
		// Return a default value instead.
		return getColourDefault(colorType);
	}
	
	/**
	 * Set a colour in the List to a custom value.
	 * @param colorType The part of the List to set the Colour for.
	 * @param value The new custom colour.
	 * 
	 * @see #HEADER
	 * @see #HEADER_ORDERED
	 * @see #LIST_A1
	 * @see #LIST_B1
	 * @see #LIST_A2
	 * @see #LIST_B2
	 * @see #LIST_HOVER
	 * @see #LIST_CLICK
	 */
	public static void setColour(int colorType, String value){
		colourSettings.put(colorType,value);
		saveSettings();
	}
	
	/**
	 * Get the default colours to colour the List in.
	 * @param colorType The part of the List to get the Colour for.
	 * @return The Default Colour for that portion of the list.
	 * 
	 * @see #HEADER
	 * @see #HEADER_ORDERED
	 * @see #LIST_A1
	 * @see #LIST_B1
	 * @see #LIST_A2
	 * @see #LIST_B2
	 * @see #LIST_HOVER
	 * @see #LIST_CLICK
	 */
	public static String getColourDefault(int colorType){
		// Return a default value instead.
		switch(colorType){
			case HEADER:
				return "#c5c5c5";
			case HEADER_ORDERED:
				return "#cfcfcf";
			case LIST_A1:
				return "#dfdfdf";
			case LIST_B1:
				return "#efefef";
			case LIST_A2:
				return "#d5d5d5";
			case LIST_B2:
				return "#e5e5e5";
			case LIST_HOVER:
				return "#f5f5f5";
			case LIST_CLICK:
				return "#ffffff";
		}
		return "#ffffff";
	}
	
	/**
	 * Convert and trim a String into a lower case version.
	 * @param string The String to remove leading or trailing spaces and convert to lower case.
	 * @return A String of the given String in a lower case format without any leading or trailing spaces.
	 */
	private static String lowerCase(String string){
		return string.trim().toLowerCase();
	}
	
	/**
	 * Returns the settings for the program at the particular key index.
	 * @param key The key that identifies the particular Object to return.
	 * @return An Object of at the particular key, null if key is not set or if value is null.
	 */
	public static Object getProgramSettings(String key){
		return programSettings.get(key);
	}
	
	/**
	 * Add or replace a value identified by key with the given Object value.
	 * @param key The identifier to identify the Objects location stored. 
	 * @param value The Object to store.
	 */
	public static void setProgramSettings(String key, Object value){
		programSettings.put(key, value);
	}
}